package sdk

import (
	"context"
	"cubeAdmin/common/store"
	"cubeAdmin/common/utils"
	"fmt"
	"github.com/beego/beego/v2/core/logs"
	beego "github.com/beego/beego/v2/server/web"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
	"io"
	"log"
	"mime/multipart"
	"net/url"
	"os"
	"strings"
	"time"
)

var (
	sevenDays int64 = 7 * 24 * 3600 //7天
)

type MinIO struct {
	AccessKey string
	SecretKey string
	Bucket    string
	Endpoint  string
	Domain    string
	Client    *minio.Client
}

// NewMinIo Minio初始化配置
func NewMinIo() (m *MinIO, err error) {

	endpoint, endpointErr := beego.AppConfig.String("minio.endpoint")
	accessKeyID, accessKeyErr := beego.AppConfig.String("minio.accessKey")
	secretAccessKey, secretAccessKeyErr := beego.AppConfig.String("minio.secretAccessKey")
	domain, domainErr := beego.AppConfig.String("minio.domain")
	bucketName, bucketNameErr := beego.AppConfig.String("minio.bucketName")
	secure, secureErr := beego.AppConfig.Bool("minio.secure")
	if endpointErr != nil {
		log.Fatal(endpointErr.Error())
	}
	if accessKeyErr != nil {
		log.Fatal(accessKeyErr.Error())
	}
	if secretAccessKeyErr != nil {
		log.Fatal(secretAccessKeyErr.Error())
	}
	if domainErr != nil {
		log.Fatal(domainErr.Error())
	}
	if bucketNameErr != nil {
		log.Fatal(bucketNameErr.Error())
	}
	if secureErr != nil {
		log.Fatal(secureErr.Error())
	}

	if domain == "" {
		domain = "http://" + endpoint
	}

	m = &MinIO{
		AccessKey: accessKeyID,
		SecretKey: secretAccessKey,
		Bucket:    bucketName,
		Endpoint:  endpoint,
		Domain:    domain,
	}

	// Initialize minio client object.
	m.Client, err = minio.New(endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
		Secure: secure,
	})

	if err != nil {
		log.Fatalln(err)
	}

	log.Printf("%#v\n", m.Client) // minioClient is now set up
	return
}

func (m *MinIO) IsExist(object string) (err error) {
	_, err = m.GetInfo(object)
	return
}

func (m *MinIO) Upload(file multipart.File, header *multipart.FileHeader, headers ...map[string]string) (info minio.UploadInfo, err error) {
	fileObj, err := header.Open()
	if err != nil {
		fmt.Println(err)
		return
	}

	opts := minio.PutObjectOptions{
		UserMetadata: make(map[string]string),
	}

	for _, header := range headers {
		for k, v := range header {
			switch strings.ToLower(k) {
			case "content-disposition":
				opts.ContentDisposition = v
			case "content-encoding":
				opts.ContentEncoding = v
			case "content-type":
				opts.ContentType = v
			default:
				opts.UserMetadata[k] = v
			}
		}
	}
	ctx := context.Background()
	file.Close()

	found, err := m.Client.BucketExists(context.Background(), m.Bucket)
	if err != nil {
		logs.Error(err.Error())
	}
	if found != true {
		logs.Info("Bucket found.")
		err = m.Client.MakeBucket(context.Background(), m.Bucket, minio.MakeBucketOptions{})
	}
	info, err = m.Client.PutObject(ctx, m.Bucket, header.Filename, fileObj, header.Size, opts)
	return
}

func (m *MinIO) Delete(prefix string) (err error) {

	objectsCh := make(chan minio.ObjectInfo)

	// Send object names that are needed to be removed to objectsCh
	go func() {
		defer close(objectsCh)
		// List all objects from a bucket-name with a matching prefix.
		opts := minio.ListObjectsOptions{
			Prefix:    prefix,
			Recursive: true,
		}
		for object := range m.Client.ListObjects(context.Background(), m.Bucket, opts) {
			if object.Err != nil {
				log.Fatalln(object.Err)
			}
			objectsCh <- object
		}
	}()

	errorCh := m.Client.RemoveObjects(context.Background(), m.Bucket, objectsCh, minio.RemoveObjectsOptions{})

	for e := range errorCh {
		log.Fatalln("Failed to remove " + e.ObjectName + ", error: " + e.Err.Error())
	}

	return
}

func (m *MinIO) GetSignURL(object string, expire int64) (link string, err error) {
	if expire <= 0 {
		link = m.Domain + utils.ObjectAbs(object)
		return
	}
	if expire > sevenDays {
		expire = sevenDays
	}
	exp := time.Duration(expire) * time.Second
	u := &url.URL{}
	u, err = m.Client.PresignedGetObject(context.Background(), m.Bucket, utils.ObjectRel(object), exp, nil)
	if err != nil {
		return
	}
	link = u.String()
	if !strings.HasPrefix(link, m.Domain) {
		link = m.Domain + u.RequestURI()
	}
	return
}

func (m *MinIO) Download(object string, savePath string) (err error) {
	obj := &minio.Object{}
	obj, err = m.Client.GetObject(context.Background(), m.Bucket, utils.ObjectRel(object), minio.GetObjectOptions{})
	if err != nil {
		return
	}

	_, err = obj.Stat()
	if err != nil {
		return
	}

	var fp *os.File
	fp, err = os.Create(savePath)
	if err != nil {
		return
	}
	defer fp.Close()

	_, err = io.Copy(fp, obj)

	return
}

func (m *MinIO) GetInfo(object string) (info store.File, err error) {
	var objInfo minio.ObjectInfo
	opts := minio.StatObjectOptions{}
	object = utils.ObjectRel(object)
	objInfo, err = m.Client.StatObject(context.Background(), m.Bucket, object, opts)
	if err != nil {
		return
	}
	info = store.File{
		LastModified: objInfo.LastModified,
		Name:         object,
		Size:         objInfo.Size,
		IsDir:        objInfo.Size == 0,
		Header:       make(map[string]string),
	}
	for k, _ := range objInfo.Metadata {
		info.Header[k] = objInfo.Metadata.Get(k)
	}
	return
}

func (m *MinIO) Lists(prefix string) (files []store.File, err error) {
	prefix = utils.ObjectRel(prefix)
	//doneCh := make(chan struct{})
	//defer close(doneCh)

	listObjectsOptions := minio.ListObjectsOptions{
		Prefix:    prefix,
		Recursive: true,
	}

	objects := m.Client.ListObjects(context.Background(), m.Bucket, listObjectsOptions)
	for object := range objects {
		header := make(map[string]string)
		file := store.File{
			LastModified: object.LastModified,
			Size:         object.Size,
			IsDir:        object.Size == 0,
			Name:         utils.ObjectRel(object.Key),
		}
		for k, _ := range object.Metadata {
			header[k] = object.Metadata.Get(k)
		}
		files = append(files, file)
	}
	return
}
